
#include "svc_test.h"

#if 0	//测试数组下标负数用法
//1.编译器会报警告:warning:  #175-D: subscript out of range
//2.最终访问的地址需要可寻址，否则fault，*** error 65: access violation at 0x1FFFDE48 : no 'write' permission
uint8_t array_1[128];
void test_array_fuzhi(void)
{
		for( int i = 0;i<128;i++ )
		{
				array_1[i] = 0xff;
		}
		uint8_t * ptr = NULL;
		array_1[-1] = 0xaa;
		array_1[-2] = 0xaa;
		ptr = &array_1[10];
		ptr[-1] = 1;
		ptr[-2] = 1;
}

#endif


void test_add(void)
{
		uint32_t a = 0x11;
		uint32_t b = 0x2200;
		uint32_t c;
		//触发SVC异常调用系统服务，实际函数调用在SVC中断中处理。这里只是在R0-R3设置好传入参数，触发SVC异常。
		c = my_add(a ,b);
		printf("a=%d ,b=%d ,c=%d=0x%x\r\n",a ,b ,c ,c);
		c = my_add3(a ,b ,c);
		printf("c=%d=0x%x\r\n",c ,c);
}


// SVC异常中断函数，汇编封皮，用于提出堆栈帧的起始位置，并放到R0中，然后跳转至实际的SVC服务例程中。
// SVC异常必须马上执行中断函数，不允许更高级中断嵌套，否则fault。
// 进入中断前，已将参数按顺序存入R0-R3。触发SVC异常后，先保存现场，硬件自动隐式push {R0-R3,R12,LR,PC,PSR} 到堆栈中(向下生长)。
// 在SVC_Handler中将进入中断之前保护好现场后的堆栈地址复制到R0。
__asm void SVC_Handler(void)
{
		IMPORT svc_real_handler
		TST LR, #4		//判断堆栈模式bit2
		ITE EQ
		MRSEQ R0, MSP
		MRSNE R0, PSP
		// 此时R0为进入中断函数前，保护好现场后的堆栈地址
		
		// 再次保存现场，显式PUSH{R4-R10,lr}到堆栈，lr寄存器为 对应模式下的 r14 连接寄存器，进入异常时自动保存异常返回地址,恢复时POP到PC。
		// 跳转svc_real_handler处理函数，R0作为第一个参数传入，其指向进入中断函数前的现场。
		B svc_real_handler
		// 不必写下BX LR来返回，而是由svc_handler来做决定
}

// 使用C写成的SVC服务例程，接受一个指针参数（ pwdSF）：进入异常中断函数前保护好现场后的堆栈的地址，栈是自上向下模型。
// pwdSF[7] = xPSR
// pwdSF[6] = 返回地址（入栈的PC）
// pwdSF[5] = LR
// pwdSF[4] = R12
// pwdSF[3] = R3
// pwdSF[2] = R2
// pwdSF[1] = R1
// pwdSF[0] = R0 <--MSP=R0=pwdSF
unsigned long svc_real_handler(unsigned int* pwdSF)
{
		unsigned int svc_number;
		unsigned int svc_para1;
		unsigned int svc_para2;
		unsigned int svc_para3;
		unsigned int svc_para4;
		int retVal = 0; //用于存储返回值
		//取压入堆栈的PC值(中断返回地址)，PC是自加的，因此可以-2推出前一条PC指令(即svc触发指令，thumb-16)的地址，其低字节为服务号(最大255)。取SVC服务号
		svc_number = ((char *) pwdSF[6])[-2]; // 没想到吧， C的数组能用得这么绝！
		//取SVC传入参数，这里只处理4个参数
		svc_para1 = ((unsigned long) pwdSF[0]);
		svc_para2 = ((unsigned long) pwdSF[1]);
		svc_para3 = ((unsigned long) pwdSF[2]);
		svc_para4 = ((unsigned long) pwdSF[3]);
		printf ("SVC number = 0x%x\n", svc_number);
		printf ("SVC parameter 0 = 0x%x\n", svc_para1);
		printf ("SVC parameter 1 = 0x%x\n", svc_para2);
		printf ("SVC parameter 2 = 0x%x\n", svc_para3);
		printf ("SVC parameter 3 = 0x%x\n", svc_para4);
		
		//根据SVC服务号，分支处理请求
		switch(svc_number)
		{
			case 0:
			{
			
			}break;
			case 1: //
			{
					retVal = svc_para1 + svc_para2;
			}break;
			case 2:
			{
					retVal = svc_para1 + svc_para2 + svc_para3;
			}break;
			case 3:
			{
			
			}break;	
			case 4:
			{
			
			}break;
			case 5:
			{
			
			}break;
			default:
					retVal = -2;
			;
		}
		
		//修改传入堆栈地址空间的R0，保存返回值。
		pwdSF[0]=retVal;
		//函数退出，恢复函数调用现场，显式POP恢复堆栈数据到{R4-R10,PC}
		return 0;//这个设置R0=0返回是无效的，因为SVC中断返回是硬件强制处理隐式POP，恢复现场再次覆盖R0。
}



//------------------------------------------- SVCALL 原理说明 ----------------------------------------------------
/*
注意，svc_handler这个函数返回的其实不是0！进一步地，灰色的文字只是用于哄编译器开心的—
—让它认为这个函数是个有返回值的函数，而且确实返回一个数值了，于是不再吵闹着说有
错或警告什么的。那返回的是啥？当然是retVal啦！有点迷糊么？往下看就知道了。
原来， SVC服务例程不能像普通的C函数那样——通过把原型声明为”unsigned int func()”,
再在末尾来一句”return xx;”来返回。因为这种常规的作法在所有的ARM中其实是把返回值
放到R0里。但是别忘了，这个函数可是异常服务例程，它的返回可是享受“异常返回”的待
遇的——伴随着一个硬件控制的自动出栈行为，这会从堆栈中重建R0的值，从而覆盖“ return”
指定的值。因此，它必须把返回值写到堆栈中R0的位置，才能借自动出栈之机返回自己的值
（ pwdSF[0]=retVal）。
这下可真相大白了！虽然内部暗流汹涌，但是从应用程序的表面上看还是风平浪静——
对于系统服务函数来说，这种独特的返回方式与普通的return xx效果是相同的，依然可以用
普通的形式接收返回值。怎么样，这招够狠吧！其实，在写系统软件时，这根本算不上耍狠，
只不过是寻常的基本功罢了，要不然怎么说C是“低级高级语言”呢。而病毒/木马所采用的
“堆栈/缓冲区溢出攻击”，那才算真正的狠招呢，但是它们原理是同根的。可见，对底层
理解得深刻，能让我们写出更好，更强大的程序来

在svc_handler中应该有调用相应函数的代码。
可以在前面加一个SVC_Table数组来保存函数指针，比如这样：
extern int __SVC_0(int, int);
extern int __SVC_1(int);
void * SVCTable[256] = {
	__SVC_0,
	__SVC_1
};


然后再这样写svc_handler中这样写：
void svc_handler(unsigned int* pwdSF) {

	// 使用栈中的r0~r3为参数调用对应的函数，
  // 并且把返回值存储到调用者的栈中r0处 
	pwdSF[0] = ((int (*)(int, int, int, int))SVCTable[((char *) pwdSF[6])[-2]])
		(
			pwdSF[0], 
			pwdSF[1], 
			pwdSF[2], 
			pwdSF[3]
		);
}


再补充一下__SVC_0的定义，在随意的某个文件中，__SVC_1不管了啊：
int __SVC_0      (int i1, int i2) {
  return (i1 + i2);
}


使用如下：
int __svc(0) add (int i1, int i2);

...

void test() {
    add(1,1);
}

int main() { ... }


这样就有了一个整体的使用svc实现系统调用的简单架构了
Re:STM32 SVCall Serval 2015-02-10 16:10  
关于“svc_number = ((char *) pwdSF[6])[-2]; // 没想到吧，C的数组能用得这么绝！ ”的用法，看了半天，贴出来大家看理解的对不对：

首先这行代码的最终目的是取得svc #num中的Num。在Thumb代码(STM32应该多数是Cortex的吧)中，一条指令通常是16位，svc #num的指令通常翻译为0xDFxx --> 其中xx就是#num。所以，Keil手册有如下解释：
0 to 224–1 (a 24-bit value) in an ARM instruction.
0-255 (an 8-bit value) in a Thumb instruction.

代码可以翻译为好懂一点的：
*((char *)pwdSF[6] - 2)

举例：
假设系统配置为小端，低位放低字节。
指令为：svc #1 ---> 二进制为0xDF01，所在地址为0x286。
那么实际上，堆栈里压入的PC，存放的是0x288。因为PC压入的是下一条指令。
0x286: DF01 ;svc #1
0x288: ......
Stack: 88 02 00 00
那行奇特的代码就是为了得到这个01。分几步：
1. 转换为(char*)pwdSF[6]，并减2
这一步是为了让指针可以精确指到88 02的位置。
(char*)pwdSF[6] - 2
2. 指针取值
*((char *)pwdSF[6] - 2)
3. 指针访问改为下表访问形式
指针取值可以写为下标形式，例如：*(p + i)等同于p[i]，个人建议指针使用时都按第二种写法
((char *) pwdSF[6])[-2]
*/

